// Write data to the IO channel - opened file descriptor, socket or SSL
// descriptor. Return number of bytes written.
static int64_t push(FILE *fp, SOCKET sock, SSL *ssl, const char *buf,
        int64_t len) {
    int64_t sent;
    int n, k;

    sent = 0;
    while (sent < len) {

        /* How many bytes we send in this iteration */
        k = len - sent > INT_MAX ? INT_MAX : (int) (len - sent);

        if (ssl != NULL) {
            n = SSL_write(ssl, buf + sent, k);
        } else if (fp != NULL) {
            n = fwrite(buf + sent, 1, (size_t)k, fp);
            if (ferror(fp))
                n = -1;
        } else {
            n = send(sock, buf + sent, (size_t)k, 0);
        }

        if (n < 0)
            break;

        sent += n;
    }

    return sent;
}

// Read from IO channel - opened file descriptor, socket, or SSL descriptor.
// Return number of bytes read.
static int pull(FILE *fp, SOCKET sock, SSL *ssl, char *buf, int len) {
    int nread;

    if (ssl != NULL) {
        nread = SSL_read(ssl, buf, len);
    } else if (fp != NULL) {
        // Use read() instead of fread(), because if we're reading from the CGI
        // pipe, fread() may block until IO buffer is filled up. We cannot afford
        // to block and must pass all read bytes immediately to the client.
        nread = read(fileno(fp), buf, (size_t) len);
        if (ferror(fp))
            nread = -1;
    } else {
        nread = recv(sock, buf, (size_t) len, 0);
    }

    return nread;
}

int mg_read(struct mg_connection *conn, void *buf, size_t len) {
    int n, buffered_len, nread;
    const char *buffered;

    assert((conn->content_len == -1 && conn->consumed_content == 0) ||
            conn->consumed_content <= conn->content_len);
    DEBUG_TRACE(("%p %zu %lld %lld", buf, len,
                conn->content_len, conn->consumed_content));
    nread = 0;
    if (conn->consumed_content < conn->content_len) {

        // Adjust number of bytes to read.
        int64_t to_read = conn->content_len - conn->consumed_content;
        if (to_read < (int64_t) len) {
            len = (int) to_read;
        }

        // How many bytes of data we have buffered in the request buffer?
        buffered = conn->buf + conn->request_len + conn->consumed_content;
        buffered_len = conn->data_len - conn->request_len;
        assert(buffered_len >= 0);

        // Return buffered data back if we haven't done that yet.
        if (conn->consumed_content < (int64_t) buffered_len) {
            buffered_len -= (int) conn->consumed_content;
            if (len < (size_t) buffered_len) {
                buffered_len = len;
            }
            memcpy(buf, buffered, (size_t)buffered_len);
            len -= buffered_len;
            buf = (char *) buf + buffered_len;
            conn->consumed_content += buffered_len;
            nread = buffered_len;
        }

        // We have returned all buffered data. Read new data from the remote socket.
        while (len > 0) {
            n = pull(NULL, conn->client.sock, conn->ssl, (char *) buf, (int) len);
            if (n <= 0) {
                break;
            }
            buf = (char *) buf + n;
            conn->consumed_content += n;
            nread += n;
            len -= n;
        }
    }
    return nread;
}

int mg_write(struct mg_connection *conn, const void *buf, size_t len) {
    return (int) push(NULL, conn->client.sock, conn->ssl,
            (const char *) buf, (int64_t) len);
}

int mg_printf(struct mg_connection *conn, const char *fmt, ...) {
    char buf[BUFSIZ];
    int len;
    va_list ap;

    va_start(ap, fmt);
    len = mg_vsnprintf(conn, buf, sizeof(buf), fmt, ap);
    va_end(ap);

    return mg_write(conn, buf, (size_t)len);
}

// URL-decode input buffer into destination buffer.
// 0-terminate the destination buffer. Return the length of decoded data.
// form-url-encoded data differs from URI encoding in a way that it
// uses '+' as character for space, see RFC 1866 section 8.2.1
// http://ftp.ics.uci.edu/pub/ietf/html/rfc1866.txt
static size_t url_decode(const char *src, size_t src_len, char *dst,
        size_t dst_len, int is_form_url_encoded) {
    size_t i, j;
    int a, b;
#define HEXTOI(x) (isdigit(x) ? x - '0' : x - 'W')

    for (i = j = 0; i < src_len && j < dst_len - 1; i++, j++) {
        if (src[i] == '%' &&
                isxdigit(* (const unsigned char *) (src + i + 1)) &&
                isxdigit(* (const unsigned char *) (src + i + 2))) {
            a = tolower(* (const unsigned char *) (src + i + 1));
            b = tolower(* (const unsigned char *) (src + i + 2));
            dst[j] = (char) ((HEXTOI(a) << 4) | HEXTOI(b));
            i += 2;
        } else if (is_form_url_encoded && src[i] == '+') {
            dst[j] = ' ';
        } else {
            dst[j] = src[i];
        }
    }

    dst[j] = '\0'; /* Null-terminate the destination */

    return j;
}

// Scan given buffer and fetch the value of the given variable.
// It can be specified in query string, or in the POST data.
// Return NULL if the variable not found, or allocated 0-terminated value.
// It is caller's responsibility to free the returned value.
int mg_get_var(const char *buf, size_t buf_len, const char *name,
        char *dst, size_t dst_len) {
    const char *p, *e, *s;
    size_t name_len, len;

    name_len = strlen(name);
    e = buf + buf_len;
    len = -1;
    dst[0] = '\0';

    // buf is "var1=val1&var2=val2...". Find variable first
    for (p = buf; p != NULL && p + name_len < e; p++) {
        if ((p == buf || p[-1] == '&') && p[name_len] == '=' &&
                !mg_strncasecmp(name, p, name_len)) {

            // Point p to variable value
            p += name_len + 1;

            // Point s to the end of the value
            s = (const char *) memchr(p, '&', (size_t)(e - p));
            if (s == NULL) {
                s = e;
            }
            assert(s >= p);

            // Decode variable into destination buffer
            if ((size_t) (s - p) < dst_len) {
                len = url_decode(p, (size_t)(s - p), dst, dst_len, 1);
            }
            break;
        }
    }

    return len;
}

int mg_get_cookie(const struct mg_connection *conn, const char *cookie_name,
        char *dst, size_t dst_size) {
    const char *s, *p, *end;
    int name_len, len = -1;

    dst[0] = '\0';
    if ((s = mg_get_header(conn, "Cookie")) == NULL) {
        return 0;
    }

    name_len = strlen(cookie_name);
    end = s + strlen(s);

    for (; (s = strstr(s, cookie_name)) != NULL; s += name_len)
        if (s[name_len] == '=') {
            s += name_len + 1;
            if ((p = strchr(s, ' ')) == NULL)
                p = end;
            if (p[-1] == ';')
                p--;
            if (*s == '"' && p[-1] == '"' && p > s + 1) {
                s++;
                p--;
            }
            if ((size_t) (p - s) < dst_size) {
                len = (p - s) + 1;
                mg_strlcpy(dst, s, (size_t)len);
            }
            break;
        }

    return len;
}

// Mongoose allows to specify multiple directories to serve,
// like /var/www,/~bob=/home/bob. That means that root directory depends on URI.
// This function returns root dir for given URI.
static int get_document_root(const struct mg_connection *conn,
        struct vec *document_root) {
    const char *root, *uri;
    int len_of_matched_uri;
    struct vec uri_vec, path_vec;

    uri = conn->request_info.uri;
    len_of_matched_uri = 0;
    root = next_option(conn->ctx->config[DOCUMENT_ROOT], document_root, NULL);

    while ((root = next_option(root, &uri_vec, &path_vec)) != NULL) {
        if (memcmp(uri, uri_vec.ptr, uri_vec.len) == 0) {
            *document_root = path_vec;
            len_of_matched_uri = uri_vec.len;
            break;
        }
    }

    return len_of_matched_uri;
}

static void convert_uri_to_file_name(struct mg_connection *conn,
        const char *uri, char *buf,
        size_t buf_len) {
    struct vec vec;
    int match_len;

    match_len = get_document_root(conn, &vec);
    mg_snprintf(conn, buf, buf_len, "%.*s%s", vec.len, vec.ptr, uri + match_len);

#if defined(_WIN32) && !defined(__SYMBIAN32__)
    change_slashes_to_backslashes(buf);
#endif /* _WIN32 */

    DEBUG_TRACE(("[%s] -> [%s], [%.*s]", uri, buf, (int) vec.len, vec.ptr));
}

static int sslize(struct mg_connection *conn, int (*func)(SSL *)) {
    return (conn->ssl = SSL_new(conn->ctx->ssl_ctx)) != NULL &&
        SSL_set_fd(conn->ssl, conn->client.sock) == 1 &&
        func(conn->ssl) == 1;
}

static struct mg_connection *mg_connect(struct mg_connection *conn,
        const char *host, int port, int use_ssl) {
    struct mg_connection *newconn = NULL;
    struct sockaddr_in sin;
    struct hostent *he;
    int sock;

    if (conn->ctx->ssl_ctx == NULL && use_ssl) {
        cry(conn, "%s: SSL is not initialized", __func__);
    } else if ((he = gethostbyname(host)) == NULL) {
        cry(conn, "%s: gethostbyname(%s): %s", __func__, host, strerror(ERRNO));
    } else if ((sock = socket(PF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {
        cry(conn, "%s: socket: %s", __func__, strerror(ERRNO));
    } else {
        sin.sin_family = AF_INET;
        sin.sin_port = htons((uint16_t) port);
        sin.sin_addr = * (struct in_addr *) he->h_addr_list[0];
        if (connect(sock, (struct sockaddr *) &sin, sizeof(sin)) != 0) {
            cry(conn, "%s: connect(%s:%d): %s", __func__, host, port,
                    strerror(ERRNO));
            closesocket(sock);
        } else if ((newconn = (struct mg_connection *)
                    calloc(1, sizeof(*newconn))) == NULL) {
            cry(conn, "%s: calloc: %s", __func__, strerror(ERRNO));
            closesocket(sock);
        } else {
            newconn->client.sock = sock;
            newconn->client.rsa.u.sin = sin;
            if (use_ssl) {
                sslize(newconn, SSL_connect);
            }
        }
    }

    return newconn;
}

// Check whether full request is buffered. Return:
//   -1  if request is malformed
//    0  if request is not yet fully buffered
//   >0  actual request length, including last \r\n\r\n
static int get_request_len(const char *buf, int buflen) {
    const char *s, *e;
    int len = 0;

    DEBUG_TRACE(("buf: %p, len: %d", buf, buflen));
    for (s = buf, e = s + buflen - 1; len <= 0 && s < e; s++)
        // Control characters are not allowed but >=128 is.
        if (!isprint(* (const unsigned char *) s) && *s != '\r' &&
                *s != '\n' && * (const unsigned char *) s < 128) {
            len = -1;
        } else if (s[0] == '\n' && s[1] == '\n') {
            len = (int) (s - buf) + 2;
        } else if (s[0] == '\n' && &s[1] < e &&
                s[1] == '\r' && s[2] == '\n') {
            len = (int) (s - buf) + 3;
        }

    return len;
}

// Convert month to the month number. Return -1 on error, or month number
static int month_number_to_month_name(const char *s) {
    size_t i;

    for (i = 0; i < ARRAY_SIZE(month_names); i++)
        if (!strcmp(s, month_names[i]))
            return (int) i;

    return -1;
}

// Parse date-time string, and return the corresponding time_t value
static time_t parse_date_string(const char *s) {
    time_t current_time;
    struct tm tm, *tmp;
    char mon[32];
    int sec, min, hour, mday, month, year;

    (void) memset(&tm, 0, sizeof(tm));
    sec = min = hour = mday = month = year = 0;

    if (((sscanf(s, "%d/%3s/%d %d:%d:%d",
                        &mday, mon, &year, &hour, &min, &sec) == 6) ||
                (sscanf(s, "%d %3s %d %d:%d:%d",
                        &mday, mon, &year, &hour, &min, &sec) == 6) ||
                (sscanf(s, "%*3s, %d %3s %d %d:%d:%d",
                        &mday, mon, &year, &hour, &min, &sec) == 6) ||
                (sscanf(s, "%d-%3s-%d %d:%d:%d",
                        &mday, mon, &year, &hour, &min, &sec) == 6)) &&
            (month = month_number_to_month_name(mon)) != -1) {
        tm.tm_mday = mday;
        tm.tm_mon = month;
        tm.tm_year = year;
        tm.tm_hour = hour;
        tm.tm_min = min;
        tm.tm_sec = sec;
    }

    if (tm.tm_year > 1900) {
        tm.tm_year -= 1900;
    } else if (tm.tm_year < 70) {
        tm.tm_year += 100;
    }

    // Set Daylight Saving Time field
    current_time = time(NULL);
    tmp = localtime(&current_time);
    tm.tm_isdst = tmp->tm_isdst;

    return mktime(&tm);
}

// Protect against directory disclosure attack by removing '..',
// excessive '/' and '\' characters
static void remove_double_dots_and_double_slashes(char *s) {
    char *p = s;

    while (*s != '\0') {
        *p++ = *s++;
        if (s[-1] == '/' || s[-1] == '\\') {
            // Skip all following slashes and backslashes
            while (*s == '/' || *s == '\\') {
                s++;
            }

            // Skip all double-dots
            while (*s == '.' && s[1] == '.') {
                s += 2;
            }
        }
    }
    *p = '\0';
}

static const struct {
    const char *extension;
    size_t ext_len;
    const char *mime_type;
    size_t mime_type_len;
} builtin_mime_types[] = {
    {".html", 5, "text/html",   9},
    {".htm", 4, "text/html",   9},
    {".shtm", 5, "text/html",   9},
    {".shtml", 6, "text/html",   9},
    {".css", 4, "text/css",   8},
    {".js",  3, "application/x-javascript", 24},
    {".ico", 4, "image/x-icon",   12},
    {".gif", 4, "image/gif",   9},
    {".jpg", 4, "image/jpeg",   10},
    {".jpeg", 5, "image/jpeg",   10},
    {".png", 4, "image/png",   9},
    {".svg", 4, "image/svg+xml",  13},
    {".torrent", 8, "application/x-bittorrent", 24},
    {".wav", 4, "audio/x-wav",   11},
    {".mp3", 4, "audio/x-mp3",   11},
    {".mid", 4, "audio/mid",   9},
    {".m3u", 4, "audio/x-mpegurl",  15},
    {".ram", 4, "audio/x-pn-realaudio",  20},
    {".xml", 4, "text/xml",   8},
    {".xslt", 5, "application/xml",  15},
    {".ra",  3, "audio/x-pn-realaudio",  20},
    {".doc", 4, "application/msword",  19},
    {".exe", 4, "application/octet-stream", 24},
    {".zip", 4, "application/x-zip-compressed", 28},
    {".xls", 4, "application/excel",  17},
    {".tgz", 4, "application/x-tar-gz",  20},
    {".tar", 4, "application/x-tar",  17},
    {".gz",  3, "application/x-gunzip",  20},
    {".arj", 4, "application/x-arj-compressed", 28},
    {".rar", 4, "application/x-arj-compressed", 28},
    {".rtf", 4, "application/rtf",  15},
    {".pdf", 4, "application/pdf",  15},
    {".swf", 4, "application/x-shockwave-flash",29},
    {".mpg", 4, "video/mpeg",   10},
    {".mpeg", 5, "video/mpeg",   10},
    {".asf", 4, "video/x-ms-asf",  14},
    {".avi", 4, "video/x-msvideo",  15},
    {".bmp", 4, "image/bmp",   9},
    {NULL,  0, NULL,    0}
};

// Look at the "path" extension and figure what mime type it has.
// Store mime type in the vector.
static void get_mime_type(struct mg_context *ctx, const char *path,
        struct vec *vec) {
    struct vec ext_vec, mime_vec;
    const char *list, *ext;
    size_t i, path_len;

    path_len = strlen(path);

    // Scan user-defined mime types first, in case user wants to
    // override default mime types.
    list = ctx->config[EXTRA_MIME_TYPES];
    while ((list = next_option(list, &ext_vec, &mime_vec)) != NULL) {
        // ext now points to the path suffix
        ext = path + path_len - ext_vec.len;
        if (mg_strncasecmp(ext, ext_vec.ptr, ext_vec.len) == 0) {
            *vec = mime_vec;
            return;
        }
    }

    // Now scan built-in mime types
    for (i = 0; builtin_mime_types[i].extension != NULL; i++) {
        ext = path + (path_len - builtin_mime_types[i].ext_len);
        if (path_len > builtin_mime_types[i].ext_len &&
                mg_strcasecmp(ext, builtin_mime_types[i].extension) == 0) {
            vec->ptr = builtin_mime_types[i].mime_type;
            vec->len = builtin_mime_types[i].mime_type_len;
            return;
        }
    }

    // Nothing found. Fall back to "text/plain"
    vec->ptr = "text/plain";
    vec->len = 10;
}

#ifndef HAVE_MD5
typedef struct MD5Context {
    uint32_t buf[4];
    uint32_t bits[2];
    unsigned char in[64];
} MD5_CTX;

#if defined(__BYTE_ORDER) && (__BYTE_ORDER == 1234)
#define byteReverse(buf, len) // Do nothing
#else
static void byteReverse(unsigned char *buf, unsigned longs) {
    uint32_t t;
    do {
        t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 |
            ((unsigned) buf[1] << 8 | buf[0]);
        *(uint32_t *) buf = t;
        buf += 4;
    } while (--longs);
}
#endif

#define F1(x, y, z) (z ^ (x & (y ^ z)))
#define F2(x, y, z) F1(z, x, y)
#define F3(x, y, z) (x ^ y ^ z)
#define F4(x, y, z) (y ^ (x | ~z))

#define MD5STEP(f, w, x, y, z, data, s) \
    ( w += f(x, y, z) + data,  w = w<<s | w>>(32-s),  w += x )

// Start MD5 accumulation.  Set bit count to 0 and buffer to mysterious
// initialization constants.
static void MD5Init(MD5_CTX *ctx) {
    ctx->buf[0] = 0x67452301;
    ctx->buf[1] = 0xefcdab89;
    ctx->buf[2] = 0x98badcfe;
    ctx->buf[3] = 0x10325476;

    ctx->bits[0] = 0;
    ctx->bits[1] = 0;
}

static void MD5Transform(uint32_t buf[4], uint32_t const in[16]) {
    register uint32_t a, b, c, d;

    a = buf[0];
    b = buf[1];
    c = buf[2];
    d = buf[3];

    MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
    MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
    MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
    MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
    MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
    MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
    MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
    MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
    MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
    MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
    MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
    MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
    MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
    MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
    MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
    MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);

    MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
    MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
    MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
    MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
    MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
    MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
    MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
    MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
    MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
    MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
    MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
    MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
    MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
    MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
    MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
    MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);

    MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
    MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
    MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
    MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
    MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
    MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
    MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
    MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
    MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
    MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
    MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
    MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
    MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
    MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
    MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
    MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);

    MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
    MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
    MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
    MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
    MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
    MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
    MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
    MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
    MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
    MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
    MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
    MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
    MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
    MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
    MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
    MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);

    buf[0] += a;
    buf[1] += b;
    buf[2] += c;
    buf[3] += d;
}

static void MD5Update(MD5_CTX *ctx, unsigned char const *buf, unsigned len) {
    uint32_t t;

    t = ctx->bits[0];
    if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t)
        ctx->bits[1]++;
    ctx->bits[1] += len >> 29;

    t = (t >> 3) & 0x3f;

    if (t) {
        unsigned char *p = (unsigned char *) ctx->in + t;

        t = 64 - t;
        if (len < t) {
            memcpy(p, buf, len);
            return;
        }
        memcpy(p, buf, t);
        byteReverse(ctx->in, 16);
        MD5Transform(ctx->buf, (uint32_t *) ctx->in);
        buf += t;
        len -= t;
    }

    while (len >= 64) {
        memcpy(ctx->in, buf, 64);
        byteReverse(ctx->in, 16);
        MD5Transform(ctx->buf, (uint32_t *) ctx->in);
        buf += 64;
        len -= 64;
    }

    memcpy(ctx->in, buf, len);
}

static void MD5Final(unsigned char digest[16], MD5_CTX *ctx) {
    unsigned count;
    unsigned char *p;

    count = (ctx->bits[0] >> 3) & 0x3F;

    p = ctx->in + count;
    *p++ = 0x80;
    count = 64 - 1 - count;
    if (count < 8) {
        memset(p, 0, count);
        byteReverse(ctx->in, 16);
        MD5Transform(ctx->buf, (uint32_t *) ctx->in);
        memset(ctx->in, 0, 56);
    } else {
        memset(p, 0, count - 8);
    }
    byteReverse(ctx->in, 14);

    ((uint32_t *) ctx->in)[14] = ctx->bits[0];
    ((uint32_t *) ctx->in)[15] = ctx->bits[1];

    MD5Transform(ctx->buf, (uint32_t *) ctx->in);
    byteReverse((unsigned char *) ctx->buf, 4);
    memcpy(digest, ctx->buf, 16);
    memset((char *) ctx, 0, sizeof(*ctx));
}
#endif // !HAVE_MD5

// Stringify binary data. Output buffer must be twice as big as input,
// because each byte takes 2 bytes in string representation
static void bin2str(char *to, const unsigned char *p, size_t len) {
    static const char *hex = "0123456789abcdef";

    for (; len--; p++) {
        *to++ = hex[p[0] >> 4];
        *to++ = hex[p[0] & 0x0f];
    }
    *to = '\0';
}

// Return stringified MD5 hash for list of vectors. Buffer must be 33 bytes.
void mg_md5(char *buf, ...) {
    unsigned char hash[16];
    const char *p;
    va_list ap;
    MD5_CTX ctx;

    MD5Init(&ctx);

    va_start(ap, buf);
    while ((p = va_arg(ap, const char *)) != NULL) {
        MD5Update(&ctx, (const unsigned char *) p, (unsigned) strlen(p));
    }
    va_end(ap);

    MD5Final(hash, &ctx);
    bin2str(buf, hash, sizeof(hash));
}
